// The MIT License

// Copyright (c) 2009 Massachusetts Institute of Technology

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/*
        method ReadCPUToHost  (out UINT32[32] regValue, in UINT32[32] dummy);
        method InstrRequestLoad  (in UINT32[32] address, out UINT32[32] value0, out UINT32[32] value1, out UINT32[32] value2, out UINT32[32] value3, out UINT32[32] value4, out UINT32[32] value5, out UINT32[32] value6, out UINT32[32] value7);
        method SendResult (in UINT32[32] flags1, in UINT32[32] addr1, in UINT32[32] value1, in UINT32[32] pc1, in UINT32[32] flags2, in UINT32[32] addr2, in UINT32[32] value2, in UINT32[32] pc2, in UINT32[32] flags3, in UINT32[32] addr3, in UINT32[32] value3, in UINT32[32] pc3, in UINT32[32] flags4, in UINT32[32] addr4, in UINT32[32] value4, in UINT32[32] pc4);
*/

import Connectable::*;
import GetPut::*;
import ClientServer::*;
import FIFO::*;
import FIFOF::*;
import SpecialFIFOs::*;

`include "awb/provides/virtual_platform.bsh"
`include "awb/provides/virtual_devices.bsh"
`include "awb/provides/common_services.bsh"
`include "awb/provides/librl_bsv.bsh"

`include "awb/provides/soft_connections.bsh"
`include "awb/provides/soft_services.bsh"
`include "awb/provides/soft_services_lib.bsh"
`include "awb/provides/soft_services_deps.bsh"

//AWB includes
`include "awb/provides/low_level_platform_interface.bsh"
//`include "asim/provides/soft_connections.bsh"
//`include "asim/provides/common_services.bsh"

// Local includes
`include "awb/provides/processor_library.bsh"
`include "awb/provides/core.bsh"
/*
`include "awb/provides/processor_library.bsh"
`include "awb/provides/fpga_components.bsh"
*/

//`include "awb/rrr/remote_client_stub_PROCESSORSYSTEMRRR.bsh"
//`include "awb/rrr/remote_server_stub_PROCESSORSYSTEMRRR.bsh"

typedef enum {Decompr1, Decompr2, Decompr3, Decompr4} DecomprStage deriving(Eq, Bits);
typedef enum {Resp1, Resp2, Resp3, Resp4} ComprStage deriving(Eq, Bits);
typedef enum {STATE_Init, STATE_Start, STATE_FileOpenRes, STATE_StreamRead, STATE_Feof, STATE_Sink} STDIOState deriving(Eq, Bits);
typedef enum {READ_flag, READ_addr, READ_value, READ_pc} READState deriving(Eq, Bits);

typedef enum {EXEC_init, EXEC_entry, EXEC_offset, EXEC_size, EXEC_waitReq, EXEC_waitRsp} EXECState deriving(Eq, Bits);

module [CONNECTED_MODULE] mkConnectedApplication ();
//	ServerStub_PROCESSORSYSTEMRRR server_stub <- mkServerStub_PROCESSORSYSTEMRRR(); 

//	ClientStub_PROCESSORSYSTEMRRR client_stub <- mkClientStub_PROCESSORSYSTEMRRR();   

	Connection_Receive#(Bool) linkStarterStartRun <- mkConnectionRecv("vdev_starter_start_run");
	Connection_Send#(Bit#(8)) linkStarterFinishRun <- mkConnectionSend("vdev_starter_finish_run");


	rule serverSink1;
//		let s <- server_stub.acceptRequest_SendToCPU();
	endrule

  Core core <- mkCore;
  Reg#(int) cycle <- mkReg(0);
  
	Reg#(STDIOState) stdio_state <- mkReg(STATE_Init);
	STDIO#(Bit#(32)) stdio_read  <- mkStdIO();
	STDIO#(Bit#(32)) stdio_write  <- mkStdIO();
	STDIO#(Bit#(32)) stdio <- mkStdIO();
	STDIO#(Bit#(32)) stdio_exec <- mkStdIO();

	Reg#(STDIO_FILE) fHandle_read <- mkRegU();
	Reg#(STDIO_FILE) fHandle_write <- mkRegU();
	Reg#(STDIO_FILE) fHandle_exec <- mkRegU();
	let tempMsg <- getGlobalStringUID("zing!\n");
	
	let fcompressed <- getGlobalStringUID("compressed.bin");
	let fdecompressed <- getGlobalStringUID("decompressed.bin");
	let fexec <- getGlobalStringUID("exec.bin");
	let fmoderb <- getGlobalStringUID("rb");
	let fmodewb <- getGlobalStringUID("wb");
	rule initSTDIO ( stdio_state == STATE_Init );
		linkStarterStartRun.deq();
		stdio_state <= STATE_Start;
		stdio.printf(tempMsg, List::nil);
	endrule

	rule startSTDIO ( stdio_state == STATE_Start );
		stdio_read.fopen_req(fcompressed, fmoderb);
		stdio_write.fopen_req(fdecompressed, fmodewb);
		//stdio_exec.fopen_req(fexec, fmoderb);
		stdio_state <= STATE_FileOpenRes;
//		stdio.printf(tempMsg, List::nil);
	endrule

	Reg#(EXECState) exec_state <- mkReg(EXEC_init);
	Reg#(Bit#(32)) exec_entry <- mkReg(0);
	Reg#(Bit#(32)) exec_offset <- mkReg(0);
	Reg#(Bit#(32)) exec_size <- mkReg(0);
	rule fropenRes(stdio_state == STATE_FileOpenRes);
		let frHandle <- stdio_read.fopen_rsp();
		fHandle_read <= frHandle;
		let fwHandle <- stdio_write.fopen_rsp();
		fHandle_write <= fwHandle;
		//let fxHandle <- stdio_exec.fopen_rsp();
		//fHandle_exec <= fxHandle;
		//stdio_exec.fread_req(fxHandle, 3);
		//exec_state <= EXEC_entry;

		stdio_state <= STATE_StreamRead;

		stdio.printf(tempMsg, List::nil);
	endrule

	rule execReadEntry(exec_state == EXEC_entry);
		let rsp <- stdio_exec.fread_rsp();

		if (rsp matches tagged Valid .v)
			exec_entry <= v;

		exec_state <= EXEC_offset;
	endrule
	rule execReadOffset(exec_state == EXEC_offset );
		let rsp <- stdio_exec.fread_rsp();
		if (rsp matches tagged Valid .v)
			exec_offset <= v;
		exec_state <= EXEC_size;
	endrule
	rule execReadSize(exec_state == EXEC_size);
		let rsp <- stdio_exec.fread_rsp();
		if (rsp matches tagged Valid .v)
			exec_size <= v;
		exec_state <= EXEC_waitReq;
	endrule

	Reg#(Bit#(16)) flying <- mkReg(0);
	rule freadReq(stdio_state == STATE_StreamRead
		&& core.get_compressedQ_count() > 16 + flying && flying == 0 );

		stdio_read.fread_req(fHandle_read, 16);
		flying <= flying + 16;
		$display("readreq");
//		stdio.printf(tempMsg, List::nil);
	endrule
	/*
        method ReqCompressed (in UINT32[32] avail);
        method ResCompressed (in UINT32[32] flags1, in UINT32[32] addr1, in UINT32[32] value1, in UINT32[32] pc1, in UINT32[32] flags2, in UINT32[32] addr2, in UINT32[32] value2, in UINT32[32] pc2, in UINT32[32] flags3, in UINT32[32] addr3, in UINT32[32] value3, in UINT32[32] pc3, in UINT32[32] flags4, in UINT32[32] addr4, in UINT32[32] value4, in UINT32[32] pc4);
	*/

	Reg#(READState) read_state <- mkReg(READ_flag);
	Reg#(Bit#(32)) read_flag <- mkReg(0);
	Reg#(Bit#(32)) read_addr <- mkReg(0);
	Reg#(Bit#(32)) read_value <- mkReg(0);
	let readMsg <- getGlobalStringUID("Read Flag!\n");
	let hexMsg <- getGlobalStringUID("%x\n");

	rule freadRsp_flag(stdio_state == STATE_StreamRead && read_state == READ_flag );
		let rsp <- stdio_read.fread_rsp();
		if (rsp matches tagged Valid .v)
		begin
			
//			stdio.printf(hexMsg, list1(v));

			read_flag <= v;
			read_addr <= 0;
			read_value <= 0;

			if ( v[30] == 1 )
			begin
				read_state <= READ_flag;
				core.put_compressed.put(
					StepRes{
						matchcount:0,	regChange:False,
						memAcc:0,	size:0,
						target:0,	done:False,
						longMatch:True, longCount:v[28:0],

						pc:0,	addr:0,	value:0 }
				);
				//stdio.printf(msg, List::nil);
				$display("done flag reached!!");
			end
			else
			if (v[18:17] > 0 )
				read_state <= READ_addr;
			else
			if (v[16] == 1 )
				read_state <= READ_value;
			else
				read_state <= READ_pc;

			flying <= flying - 1;
		end
		else
			stdio_state <= STATE_Feof;
		
//		syncing <= True;
		$display("readrsp_flag");
	endrule


	rule freadRsp_addr(stdio_state == STATE_StreamRead && read_state == READ_addr );
		flying <= flying - 1;
		
		let rsp <- stdio_read.fread_rsp();
		if (rsp matches tagged Valid .v)
		begin
			read_addr <= v;
			if (read_flag[16] == 1 )
				read_state <= READ_value;
			else
				read_state <= READ_pc;
		end
		else
			stdio_state <= STATE_Feof;
		
//		syncing <= True;
		$display("readrsp_addr");
	endrule
	rule freadRsp_value(stdio_state == STATE_StreamRead && read_state == READ_value );
		flying <= flying - 1;
		
		let rsp <- stdio_read.fread_rsp();
		if (rsp matches tagged Valid .v)
		begin
			read_value <= v;
			read_state <= READ_pc;
		end
		else
			stdio_state <= STATE_Feof;
		
//		syncing <= True;
		$display("readrsp_value");
	endrule
	rule freadRsp_pc(stdio_state == STATE_StreamRead && read_state == READ_pc);
		flying <= flying - 1;
		
		let rsp <- stdio_read.fread_rsp();
		if (rsp matches tagged Valid .v)
		begin
		
			if ( read_flag[31] == 1 )
				$display("done?!");
//			if ( read_flag[15:0] > 0 )
//				$display("predict:%d", read_flag[15:0]); 

				core.put_compressed.put(
				StepRes{
					matchcount:read_flag[15:0],
					regChange:(read_flag[16] == 1),
					memAcc:read_flag[18:17],
					size:read_flag[23:19],
					target:read_flag[28:24],
					done:(read_flag[31] == 1),
					longMatch:(read_flag[30] == 1),
					longCount:(read_flag[28:0]),

					pc:v,
					addr:read_addr,
					value:read_value
				}
			);
			core.add_compressedQ_count();
			read_state <= READ_flag;
		end
		else
			stdio_state <= STATE_Feof;

//		syncing <= True;
		$display("readrsp_pc");
	endrule

	rule fread_Feof_Sink(stdio_state == STATE_Sink);
		let rsp <- stdio_read.fread_rsp();
	endrule

	rule fread_Feof(stdio_state == STATE_Feof);
		//let rsp <- stdio_read.fread_rsp();
		$display("FEOF reached!!");
		core.put_compressed.put(
			StepRes{
				matchcount:0,	regChange:False,
				memAcc:0,	size:0,
				target:0,	done:True,
				longMatch:False, longCount:0,

				pc:0,	addr:0,	value:0 }
		);
		stdio_state <= STATE_Sink;
		//linkStarterFinishRun.send(0);
	endrule


	let writeMsg <- getGlobalStringUID("write Entry!\n");
	rule writeDecompressed;
		let res <- core.get_decompressed.get;

		$display("Writing!\n");
		stdio_write.fwrite( fHandle_write, list4(
		{pack(res.done), 2'b00, res.target, res.size, res.memAcc, 
		pack(res.regChange), /*res.matchcount,*/ 16'h00},
		res.addr,
		res.value,
		res.pc));
		//stdio.printf(writeMsg, List::nil);
		if ( res.done ) begin
			linkStarterFinishRun.send(0);
		end
		
	endrule

	rule sendInstReq(exec_state == EXEC_waitReq);
		let coreReq <- core.imem_client.request.get;

		case (coreReq) matches 
			tagged LoadReq .load: begin
				$display("fetching %x", load.addr);
				//stdio_exec.fread_req(fxHandle, 7);
//				client_stub.makeRequest_InstrRequestLoad(load.addr);
			end
		endcase
  endrule

	Reg#(Bit#(3)) instrFetchIdx <- mkReg(0);
	Reg#(Bit#(32)) v1 <- mkReg(0);
	Reg#(Bit#(32)) v2 <- mkReg(0);
	Reg#(Bit#(32)) v3 <- mkReg(0);
	Reg#(Bit#(32)) v4 <- mkReg(0);
	Reg#(Bit#(32)) v5 <- mkReg(0);
	Reg#(Bit#(32)) v6 <- mkReg(0);
	Reg#(Bit#(32)) v7 <- mkReg(0);
  
	rule receiveInstResp(instrFetchIdx == 0);
//    let resp <- client_stub.getResponse_InstrRequestLoad();
		/*v1 <= resp.value1;
		v2 <= resp.value2;
		v3 <= resp.value3;
		v4 <= resp.value4;
		v5 <= resp.value5;
		v6 <= resp.value6;
		v7 <= resp.value7;
		instrFetchIdx <= 1;
    core.imem_client.response.put(tagged LoadResp {data:resp.value0,
                                                   tag: 0});
																									 */
  endrule

	rule instrFetchFeed(instrFetchIdx > 0);
		if ( instrFetchIdx < 7 )
			instrFetchIdx <= instrFetchIdx + 1;

		if ( instrFetchIdx == 1 ) 
    	core.imem_client.response.put(tagged LoadResp {data:v1, tag: 1});
		else if ( instrFetchIdx == 2 ) 
    	core.imem_client.response.put(tagged LoadResp {data:v2, tag: 2});
		else if ( instrFetchIdx == 3 ) 
    	core.imem_client.response.put(tagged LoadResp {data:v3, tag: 3});
		else if ( instrFetchIdx == 4 ) 
    	core.imem_client.response.put(tagged LoadResp {data:v4, tag: 4});
		else if ( instrFetchIdx == 5 ) 
    	core.imem_client.response.put(tagged LoadResp {data:v5, tag: 5});
		else if ( instrFetchIdx == 6 ) 
    	core.imem_client.response.put(tagged LoadResp {data:v6, tag: 6});
		else if ( instrFetchIdx == 7 ) begin
    	core.imem_client.response.put(tagged LoadResp {data:v7, tag: 7});
			instrFetchIdx <= 0;
		end
	endrule
endmodule
